/*
 * Copyright (c) 2011.
 *     This program is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 *
 *     This program is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License for more details.
 *
 *     You should have received a copy of the GNU General Public License
 *     along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.graphoman.gui;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;
import org.graphoman.Action;
import org.graphoman.math.Calculator;
import org.graphoman.model.Model;
import org.graphoman.model.State;
import org.graphoman.model.Transition;
import org.graphoman.undo.UndoAction;

import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * Created by IntelliJ IDEA. User: daniil Date: 10.10.11 Time: 10:08 To change
 * this template use File | Settings | File Templates.
 */
public class MainForm extends JFrame {
    JPanel viewPanel;
    Model model;
    Point mousePressedCoordinates = new Point();
    Point mouseCurrentCoordinates = new Point();
    Point startedDraggingStatePoint = new Point();
    Point startedDraggingViewport = new Point();

    boolean leftMouseButtonPressed = false;
    boolean creatingNewTransition = false;

    List<UndoAction> undoActionList;

    Action refresh;

    public MainForm() {

        setTitle("Welcome, graphoman!");
        XStream xStream = new XStream(new DomDriver());
        //model = (Model) xStream.fromXML(new File("startup.xml"));
        model = Model.createTestModel();

        undoActionList = new ArrayList<UndoAction>();
        createLayout(this);
        createMenu(this);
        createToolBar(this);
        addComponentListener(new ComponentAdapter() {
            @Override
            public void componentResized(ComponentEvent e) {
                Render.buffer = new BufferedImage(viewPanel.getWidth(), viewPanel.getHeight(), BufferedImage.TYPE_INT_RGB);
                repaintModel();
            }
        });

        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        setSize(1024, 768);
        setLocationRelativeTo(null);
        setResizable(true);
        // hack - paint method must be invoked AFTER the window is shown
        setVisible(true);
        Render.renderModel(model, viewPanel.getGraphics(), mouseCurrentCoordinates, creatingNewTransition);


    }

    private void createLayout(JFrame frame) {
        frame.setLayout(new BorderLayout());
        viewPanel = new JPanel();
        viewPanel.setPreferredSize(new Dimension(1024, 768));
        viewPanel.addMouseListener(new MouseAdapter() {

            /*
            тут надо отследить:
            2 клика - открытие диалога с редактированием состояния или перехода
            (тут надо подумать - выходить из метода или идти дальше)
            1 клик - выбор состояния или перехода
            либо
            ничего если в пустое место - сбросить выделение
            */
            @Override
            public void mousePressed(MouseEvent e) {

                mousePressedCoordinates = e.getPoint();
                mouseCurrentCoordinates = e.getPoint();

                if (e.getButton() == MouseEvent.BUTTON1) {
                    leftMouseButtonPressed = true;
                    for (State state : model.states) {
                        // mouse pointer is on the state
                        if (model.viewport.getModelCoordinates(e.getPoint()).distance(state) < state.diameter / 2) {
                            model.selectedState = state;
                            startedDraggingStatePoint = new Point(state);
                            break;
                        }
                    }
                    if (model.selectedState == null)
                        for (Transition transition : model.transitions) {
                            if (model.viewport.getModelCoordinates(e.getPoint()).distance(
                                    transition) < 20) {
                                model.selectedTransition = transition;
                                startedDraggingStatePoint = new Point(transition);
                                break;
                            }
                        }
                    if (model.selectedState == null && model.selectedTransition == null) {
                        startedDraggingViewport = new Point(model.viewport.x, model.viewport.y);
                    }
                } else if (e.getButton() == MouseEvent.BUTTON3) {
                    creatingNewTransition = true;
                    for (State state : model.states) {
                        if (model.viewport.getModelCoordinates(e.getPoint()).distance(state) < state.diameter / 2) {
                            model.createTransitionFrom = state;
                            break;
                        }
                    }
                }
                Render.renderModel(model, viewPanel.getGraphics(), mouseCurrentCoordinates,
                        creatingNewTransition);
            }

            /*
            при отпускании надо отследить создание перехода,
            сброс режимов -  перетаскивания и проч
             */
            @Override
            public void mouseReleased(MouseEvent e) {
                leftMouseButtonPressed = false;
                creatingNewTransition = false;
                if (e.getClickCount() == 2) {
                    if (model.selectedState != null) {
                        StateDialog.showEditDialog(refresh, model, model.selectedState,undoActionList);
                    } else if (model.selectedTransition != null) {
                        TransitionDialog.showEditDialog(refresh, model, model.selectedTransition,undoActionList);
                        /*
                                                */
                    }

                }
                model.selectedState = null;
                model.selectedTransition = null;

                if (model.createTransitionFrom != null) {
                    for (State state : model.states) {
                        if (model.viewport.getModelCoordinates(e.getPoint()).distance(state) < state.diameter / 2) {
                            model.createTransitionTo = state;
                            break;
                        }
                    }
                    if (model.createTransitionTo != null) {
                        Transition transition = new Transition(model.createTransitionFrom,
                                model.createTransitionTo, "1");
                        TransitionDialog.showCreateDialog(refresh, model, transition, undoActionList);

                    }
                }
                model.createTransitionFrom = null;
                model.createTransitionTo = null;
                Render.renderModel(model, viewPanel.getGraphics(), mouseCurrentCoordinates,
                        creatingNewTransition);

            }
        });
        viewPanel.addMouseMotionListener(new MouseAdapter() {
            /*
            тут надо отследить перетаскивание
            состояний или переходов или вьюпорта
             */
            @Override
            public void mouseDragged(MouseEvent e) {
                mouseCurrentCoordinates = e.getPoint();
                int dx = (int) ((e.getPoint().x - mousePressedCoordinates.x) / model.viewport.scale);
                int dy = (int) ((e.getPoint().y - mousePressedCoordinates.y) / model.viewport.scale);

                if (model.selectedState != null) {
                    model.selectedState.x = startedDraggingStatePoint.x + dx;
                    model.selectedState.y = startedDraggingStatePoint.y + dy;

                } else if (model.selectedTransition != null) {
                    model.selectedTransition.x = startedDraggingStatePoint.x + dx;
                    model.selectedTransition.y = startedDraggingStatePoint.y + dy;
                } else if (leftMouseButtonPressed) {
                    // move model.viewport
                    model.viewport.x = startedDraggingViewport.x + dx;
                    model.viewport.y = startedDraggingViewport.y + dy;
                }

                Render.renderModel(model, viewPanel.getGraphics(), mouseCurrentCoordinates,
                        creatingNewTransition);

            }
        });
        viewPanel.addMouseWheelListener(new MouseWheelListener() {

            public void mouseWheelMoved(MouseWheelEvent e) {

                float oldScale = model.viewport.scale;
                model.viewport.scale *= (1 + ((float) e.getWheelRotation()) / -6f);
                Point fc = e.getPoint();
                model.viewport.x += fc.x / model.viewport.scale - fc.x / oldScale;
                model.viewport.y += fc.y / model.viewport.scale - fc.y / oldScale;
                Render.renderModel(model, viewPanel.getGraphics(), mouseCurrentCoordinates,
                        creatingNewTransition);

            }
        });
        viewPanel.setFocusable(true);
        viewPanel.requestFocusInWindow();

        refresh = new Action() {
            public Object perform() {
                Render.renderModel(model, viewPanel.getGraphics(), mouseCurrentCoordinates,
                        creatingNewTransition);
                return null;
            }
        };

        frame.add(viewPanel, BorderLayout.CENTER);

    }

    private void createToolBar(JFrame frame) {
        JToolBar toolBar = new JToolBar();

        JButton saveProject = new JButton(new ImageIcon("icons/oxygen/filesave.png"));
        saveProject.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent actionEvent) {
                JFileChooser chooser = new JFileChooser(".");
                chooser.showSaveDialog(null);
                chooser.setMultiSelectionEnabled(false);
                if (chooser.getSelectedFile() != null) {
                    XStream xStream = new XStream(new DomDriver());
                    try {
                        xStream.toXML(model, new FileWriter(chooser.getSelectedFile()));
                    } catch (IOException e) {
                        JOptionPane.showMessageDialog(
                                null,
                                "Error saving " + chooser.getSelectedFile().getName(),
                                "Error!",
                                JOptionPane.ERROR_MESSAGE);
                    }
                }
            }
        });
        toolBar.add(saveProject);

        JButton openProject = new JButton(new ImageIcon("icons/oxygen/fileopen.png"));
        openProject.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent actionEvent) {
                JFileChooser chooser = new JFileChooser(".");
                chooser.setMultiSelectionEnabled(false);
                chooser.showOpenDialog(null);
                if (chooser.getSelectedFile() != null) {
                    XStream xStream = new XStream(new DomDriver());
                    model = (Model) xStream.fromXML(chooser.getSelectedFile());
                    Render.renderModel(model, viewPanel.getGraphics(), mouseCurrentCoordinates,
                            creatingNewTransition);
                    if (model == null) {
                        model = new Model();
                    }

                }
            }
        });
        toolBar.add(openProject);

        JButton undoButton = new JButton(new ImageIcon("icons/oxygen/undo.png"));
        undoButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent actionEvent) {
                if (undoActionList.size() > 0) {
                    undoActionList.get(undoActionList.size() - 1).undo(model);
                    undoActionList.remove(undoActionList.size() - 1);
                    repaintModel();
                }
            }
        });
        toolBar.add(undoButton);

        toolBar.add(new JToolBar.Separator());

        JButton fitModel = new JButton(new ImageIcon("icons/oxygen/viewmagfit.png"));
        fitModel.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent arg0) {
                if (model.states != null && !model.states.isEmpty()) {

                    int xmin = model.states.get(0).x - model.states.get(0).diameter;
                    int xmax = model.states.get(0).x + model.states.get(0).diameter;
                    int ymin = model.states.get(0).y - model.states.get(0).diameter;
                    int ymax = model.states.get(0).y + model.states.get(0).diameter;
                    for (State state : model.states) {
                        if (state.x - state.diameter < xmin)
                            xmin = state.x - state.diameter;
                        if (state.x + state.diameter > xmax)
                            xmax = state.x + state.diameter;
                        if (state.y - state.diameter < ymin)
                            ymin = state.y - state.diameter;
                        if (state.y + state.diameter > ymax)
                            ymax = state.y + state.diameter;
                    }
                    model.viewport.x = -xmin;
                    model.viewport.y = -ymin;
                    if (xmax - xmin != 0 && ymax - ymin != 0) {
                        float xscale = (float) viewPanel.getSize().width / (xmax - xmin);
                        float yscale = (float) viewPanel.getSize().height / (ymax - ymin);
                        model.viewport.scale = xscale > yscale ? yscale : xscale;
                    } else {
                        model.viewport.scale = 1;
                    }
                }
                Render.renderModel(model, viewPanel.getGraphics(), mouseCurrentCoordinates,
                        creatingNewTransition);

            }
        });
        toolBar.add(fitModel);

        JButton resetZoom = new JButton(new ImageIcon("icons/oxygen/viewmag.png"));
        resetZoom.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent actionEvent) {
                model.viewport.scale = 1.0f;
                model.viewport.x = 0;
                model.viewport.y = 0;
                Render.renderModel(model, viewPanel.getGraphics(), mouseCurrentCoordinates,
                        creatingNewTransition);
            }
        });
        toolBar.add(resetZoom);

        toolBar.add(new JToolBar.Separator());

        JButton addStateButton = new JButton(new ImageIcon("icons/state.png"));
        addStateButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent actionEvent) {
                StateDialog.showCreateDialog(refresh, model, undoActionList);
            }
        });
        toolBar.add(addStateButton);

        JButton addTransitionButton = new JButton(new ImageIcon("icons/arrow.png"));
        addTransitionButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent actionEvent) {
                TransitionDialog.showCreateDialog(refresh, model, new Transition(),undoActionList);
            }
        });
        toolBar.add(addTransitionButton);

        JButton savePngImage = new JButton("PNG");
        savePngImage.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                JFileChooser chooser = new JFileChooser(".");
                chooser.setMultiSelectionEnabled(false);
                chooser.showSaveDialog(null);
                if (chooser.getSelectedFile() != null) {
                    try {
                        ImageIO.write(Render.buffer, "png", chooser.getSelectedFile());
                    } catch (IOException e1) {
                        System.out.println(e1.getMessage());
                        JOptionPane.showMessageDialog(null,e1.getMessage());
                    }
                }
            }
        });
        toolBar.add(savePngImage);

        JButton calculateButton = new JButton("calc");
        calculateButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                Calculator.calculateTransitionMatrix(model);

            }
        });
        toolBar.add(calculateButton);

        // todo
        JButton saveBitmapButton = new JButton("savePNG");
        //toolBar.add(saveBitmapButton);

        frame.add(toolBar, BorderLayout.PAGE_START);
    }

    private void createMenu(JFrame frame) {
        // todo
    }

    private void repaintModel() {
        Render.renderModel(model, viewPanel.getGraphics(), mouseCurrentCoordinates, creatingNewTransition);
    }

}
